#!/usr/bin/perl
#
# blackhole6: A tool to make complex IPv6 tasks easy
#
# Syntax: blackhole6 DESTINATION [HEADERSIZE [PROTOCOL [PORT]]]

use Socket();

$SI6TOOLKIT="SI6 Networks IPv6 Toolkit v3.0";

$total=0;
$response=0;
$timeout=0;

sub usage{
	print "usage: blackhole6 DESTINATION [EHTYPE[EHSIZE]] [PROTOCOL [PORT]]]\n";
}


# Function GetASN()
#
# Obtains the autonomous system number (ASN) for a given IPv6 address
#
sub GetASN{
	@revname=`addr6 -a $_[0] -r`;

	if( ($? >> 8) != 0){
		return(-1);
	}
	else{
		chomp($revname[0]);
		$queryname= $revname[0] . ".origin6.asn.cymru.com.";
		@reverse=`host -t TXT $queryname`;

		if($reverse[0] =~ m/\"\s*((\d+)\s*)\s+|"/){
			return($2);
		}
		else{
			return(-1);
		}
	}
}

# Function GetORG()
#
# Obtains the organization corresponding to an autonomous system number (ASN)
#
sub GetORG{
	$querystring="host -t TXT AS".$_[0].".asn.cymru.com";
	@asinfo= `$querystring`;

	if($asinfo[0] =~ m/\"*.\|.*\|.*\|.*\|\s*(.*)\"/){
		return($1);
	}
	else{
		return(-1);
	}
}

# Remove all items from the array of IP addresses
undef @ipsfree;

print "$SI6TOOLKIT\n";
print "blackhole6: A tool to find IPv6 blackholes\n";

if($> != 0){
	print "Error: blackhole6 requires superuser privileges\n";
	exit 1;
}

if($#ARGV < 0){
	print "Error: Must specify an IPv6 address\n";
	usage();
	exit 1;
}


# Obtain the IPv6 addres corresponding to the specified domain name
# (This implicitly obtains the canonic address if an IPv6 address was specified)
my ( $err, @addrs ) = Socket::getaddrinfo( $ARGV[0], 0, { 'protocol' => Socket::IPPROTO_TCP, 'family' => Socket::AF_INET6 } );

if($err){
	die $err;
}

my ( $err, $fline ) = Socket::getnameinfo( $addrs[0]->{addr}, Socket::NI_NUMERICHOST );
if ($err){
	die $err;
}

#$fline= Socket::inet_ntop(Socket::AF_INET6, $addrs[0]->{addr});

$ehtype= "do";
$ehsize= "8";
$prototype="icmp";

if($#ARGV > 0){
	if($ARGV[1] =~ m/([a-zA-Z]+):?(\d+)/){
		$ehtype= lc($1);
		$ehsize= $2;
	}
	else{
		$ehtype= $ARGV[1];
	}

	if($#ARGV > 1){
		$prototype=	$ARGV[2];

		if($#ARGV > 2){
			$port=	$ARGV[3];
		}
		else{
			$port= "80";
		}
	}
}

if($prototype eq "tcp"){
	$protoopt= "--tcp-flags S -a $port";
}
else{
	$protoopt="";
}

if($ehtype eq "fh" || $ehtype eq "frag"){
	if($ehsize < 8){
		print "Error: Fragment size should be larger than or equal to 8 bytes";
		exit 1;
	}
	elsif($ehsize > 1280){
		print "Error: Fragment size should be smaller than or equal to 1280 bytes";
		exit 1;
	}

	$payload= ($ehsize * 2) - 20;
	$protoopt="-P $payload " . $protoopt;
}

if($ehtype eq "fh" || $ehtype eq "frag"){
	$eh= "-y $ehsize";
}
elsif($ehtype eq "hbh"){
	$eh= "-H $ehsize";
}
elsif($ehtype eq "do"){
	$eh= "-u $ehsize";
}
elsif($ehtype eq "esp"){
	$eh= "-p esp";
}
elsif($ehtype eq "ah"){
	$eh= "-p ah";
}
else{
	print "Error: Unknown EH type";
	exit 1;
}

print "Tracing $ARGV[0] ($fline)...\n";

$maxhopsfree=0;
$maxhopsfreeip="";
@tcp=`path6 -d $fline -p $prototype $protoopt --rate-limit 40pps`;

if(($? >> 8) != 0){
	print "blackhole6: Found issues when running path6 to the specified target";
	exit(1);
}

foreach $line (@tcp){
	# Discard lines that do not contain a "probe" line
	if($line =~ m/\s+(\d+)\s+\((\S*)\)/){
		if($1 > $maxhopsfree){
			if($2 ne ""){
				$maxhopsfree=$1;
				$maxhopsfreeip=$2;
			}

			# We store the IPv6 addresses of all hops
			push(@ipsfree, $2);
		}
	}
}

$maxhopstrouble=0;
$maxhopstroubleip="";

# XXX: This is an ugly hack. should be removed. ESP and AH should possibly be handled as EHs, rather than probe types
if($ehtype eq "esp" || $ehtype eq "ah"){
	@tcp=`path6 -d $fline $eh --rate-limit 40pps`;
}else{
	@tcp=`path6 -d $fline -p $prototype $protoopt $eh --rate-limit 40pps`;
}


if(($? >> 8) != 0){
	print "blackhole6: Found issues when running path6 to the specified target\n";
	exit(1);
}

foreach $line (@tcp){
	# Discard lines that do not contain a "probe" line
	if($line =~ m/\s+(\d+)\s+\((\S*)\)/){
		if($1 > $maxhopstrouble){
			if($2 ne ""){
				$maxhopstrouble=$1;
				$maxhopstroubleip= $2;
			}
		}
	}
}

$dropip="";
$dropip2="";

for($i=0; $i< $#ipsfree; $i++){
	if($ipsfree[$i] eq $maxhopstroubleip){
		$dropip= $ipsfree[$i+1];
		$dropip2= $ipsfree[$i+2];
		last;
	}
}

$flineasn= GetASN($fline);

# fline holds the destination system
if($flineasn == -1){
#	print "blackhole6: Error when trying to obtain ASN for $fline\n";
#	exit(1);
	$flineasn= " Unknown";
}

elsif($flineasn eq ""){
	$flineasn= " Unknown";
}


$maxhopsfreeasn= GetASN($maxhopsfreeip);

if($maxhopsfreeasn == -1){
#	print "blackhole6: Error when trying to obtain ASN for $maxhopsfreeip\n";
#	exit(1);
	$maxhopsfreeasn= " Unknown";
}

elsif($maxhopsfreeasn eq ""){
	$maxhopsfreeasn= " Unknown";
}

$maxhopstroubleasn= GetASN($maxhopstroubleip);

if($maxhopstroubleasn == -1){
#	print "blackhole6: Error when trying to obtain ASN for $maxhopstroubleip\n";
#	exit(1);
	$maxhopstroubleasn= " Unknown";
}
elsif($maxhopstroubleasn eq ""){
	$maxhopstroubleasn= " Unknown";
}

$ehtype_uc= uc($ehtype);
$flineorg= GetORG($flineasn);

if($flineorg == -1){
#	print "blackhole6: Error when trying to obtain organization for AS$flineasn\n";
#	exit(1);
	$flineorg= "Unknown organization";
}

$maxhopsfreeorg= GetORG($maxhopsfreeasn);

if($maxhopsfreeorg == -1){
#	print "blackhole6: Error when trying to obtain organization for AS$maxhopsfreeasn\n";
#	exit(1);
	$maxhopsfreeorg= "Unknown organization";
}

$maxhopstroubleorg= GetORG($maxhopstroubleasn);

if($maxhopstroubleorg == -1){
#	print "blackhole6: Error when trying to obtain organization for AS$maxhopstroubleasn\n";
#	exit(1);
	$maxhopstroubleorg=  "Unknown organization";
}

print "\nDst. IPv6 address: $fline (AS$flineasn - $flineorg)\n";
print "Last node (no EHs): $maxhopsfreeip (AS$maxhopsfreeasn - $maxhopsfreeorg) ($maxhopsfree hop(s))\n";
print "Last node ($ehtype_uc $ehsize): $maxhopstroubleip (AS$maxhopstroubleasn - $maxhopstroubleorg) ($maxhopstrouble hop(s))\n";

if($maxhopsfreeip eq $fline){
	if($maxhopstroubleip eq $fline){
			print "Dropping node: No packet drops\n";
	}
	else{
		$dropasn= GetASN($dropip);
		if($dropasn == -1){
#			print "blackhole6: Error when trying to obtain ASN for $dropip\n";
#			exit(1);
			$dropasn="";
		}

		$droporg= "";

		if($dropasn ne ""){
			$droporg= GetORG($dropasn);
			if($droporg == -1){
#				print "blackhole6: Error when trying to obtain organization for AS$dropasn\n";
#				exit(1);
				$droporg= "";
			}
		}
		
		if($dropasn eq ""){
			$dropasn= " Unknown";
		}

		if($droporg eq ""){
			$droporg= "Unknown";
		}

		if($dropip2 ne ""){
			$dropasn2= GetASN($dropip2);
			if($dropasn2 == -1){
#				print "blackhole6: Error when trying to obtain ASN for $dropip2\n";
#				exit(1);
				$dropasn2="";
			}

			$droporg2= "";

			if($dropasn2 ne ""){
				$droporg2= GetORG($dropasn2);
				if($droporg2 == -1){
#					print "blackhole6: Error when trying to obtain organization for AS$dropasn2\n";
#					exit(1);
					$droporg2="";
				}
			}
		
			if($dropasn2 eq ""){
				$dropasn2= " Unknown";
			}

			if($droporg2 eq ""){
				$droporg2= "Unknown";
			}
		}

		if( ($dropip2 eq "") || ($dropasn1 eq $dropasn2)){
			print "Dropping node: $dropip (AS$dropasn - $droporg)\n";
		}
		else{
			print "Dropping node: $dropip (AS$dropasn - $droporg || AS$dropasn2 - $droporg2)\n";
		}
	}
}
else{
	print "Dropping nodes: Packets being dropped for both the no-EH and the EH case\n";
}

